package cn.yx.common.core.util.http.httpclient;

import cn.yx.common.core.enums.CharsetEnum;
import cn.yx.common.core.enums.ContentTypeEnum;
import cn.yx.common.core.enums.MimeTypeEnum;
import cn.yx.common.core.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * <p>HttpPost操作类</p>
 * <p>封装了部分可直接调用的post方法，如果没有合适的post方法，则可以调用buildPost方法获得HttpPost对象后，再调用sendPost方法。</p>
 *
 * @author Wgssmart
 */
@Slf4j
public class HttpPostUtil extends HttpBasic {

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url url,可包含查询字符串
     * @return 指定url所返回的字符串数据
     */
    public static String post(String url) {
        return sendPost(buildHttpPost(url, null), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url     url,可包含查询字符串
     * @param headers headers头部
     * @return 指定url所返回的字符串数据
     */
    public static String post(String url, Map<String, String> headers) {
        return sendPost(buildHttpPost(url, headers), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url    url,可包含查询字符串
     * @param params post请求发送的参数，参数为param1=value1&param2=value2形式
     * @return 请求返回相应的字符串数据
     */
    public static String post(String url, String params) {
        return sendPost(buildHttpPost(url, null, params), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url     url,可包含查询字符串
     * @param headers headers头部
     * @param params  post请求发送的参数，参数为param1=value1&param2=value2形式
     * @return 请求返回相应的字符串数据
     */
    public static String post(String url, Map<String, String> headers, String params) {
        return sendPost(buildHttpPost(url, headers, params), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url    url,可包含查询字符串
     * @param params key-value结构的参数
     * @return 指定url响应的字符串数据
     */
    public static String postParams(String url, Map<String, String> params) {
        return sendPost(buildHttpPost(url, null, params), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url     url,可包含查询字符串
     * @param headers headers头部
     * @param params  key-value结构的参数
     * @return 指定url响应的字符串数据
     */
    public static String postParams(String url, Map<String, String> headers, Map<String, String> params) {
        return sendPost(buildHttpPost(url, headers, params), STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url          url,可包含查询字符串
     * @param params       key-value结构的参数
     * @param cookieList   需要提交到服务端的cookie数据，如果为null，则不提交cookie
     * @param returnCookie 判断是否需要从服务端获取cookie数据
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, Map<String, String> params, List<Cookie> cookieList, boolean returnCookie) {
        return sendPost(buildHttpPost(url, null, params), cookieList, returnCookie, STRING_RESPONSE_HANDLER);
    }

    /**
     * HTTP POST请求访问指定的url
     *
     * @param url          url,可包含查询字符串
     * @param headers      headers头部
     * @param params       key-value结构的参数
     * @param cookieList   需要提交到服务端的cookie数据，如果为null，则不提交cookie
     * @param returnCookie 判断是否需要从服务端获取cookie数据
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params,
                              List<Cookie> cookieList, boolean returnCookie) {
        return sendPost(buildHttpPost(url, headers, params), cookieList, returnCookie, STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过post请求上传单个文件
     *
     * @param url       请求地址，可包含查询字符串
     * @param params    请求参数
     * @param inputName 上传文件的input name
     * @param filePath  上传文件的完整路径
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> params, String inputName, String filePath) {
        return post(url, params, inputName, new File(filePath));
    }

    /**
     * 通过post请求上传单个文件
     *
     * @param url       请求地址，可包含查询字符串
     * @param headers   headers头部
     * @param params    请求参数
     * @param inputName 上传文件的input name
     * @param filePath  上传文件的完整路径
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params,
                              String inputName, String filePath) {
        return post(url, headers, params, inputName, new File(filePath));
    }

    /**
     * 通过post请求上传单个文件
     *
     * @param url       请求地址，可包含查询字符串
     * @param params    请求参数
     * @param inputName 上传文件的input name
     * @param file      上传的文件，为File类型
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> params, String inputName, File file) {
        Map<String, File> files = new HashMap<>(1);
        files.put(inputName, file);
        return post(url, params, files);
    }

    /**
     * 通过post请求上传单个文件
     *
     * @param url       请求地址，可包含查询字符串
     * @param headers   headers头部
     * @param params    请求参数
     * @param inputName 上传文件的input name
     * @param file      上传的文件，为File类型
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params,
                              String inputName, File file) {
        Map<String, File> files = new HashMap<>(1);
        files.put(inputName, file);
        return post(url, headers, params, files);
    }

    /**
     * 通过post请求上传多个文件
     *
     * @param url    请求地址，可包含查询字符串
     * @param params 请求参数
     * @param files  上传的文件，为Map结构，键为上传文件时对应的input name，值为File类型的文件
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> params, Map<String, File> files) {
        return sendPost(buildHttpPost(url, null, params, files), STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过post请求上传多个文件
     *
     * @param url     请求地址，可包含查询字符串
     * @param headers headers头部
     * @param params  请求参数
     * @param files   上传的文件，为Map结构，键为上传文件时对应的input name，值为File类型的文件
     * @return 请求响应返回的字符串数据
     */
    public static String post(String url, Map<String, String> headers, Map<String, String> params, Map<String, File> files) {
        return sendPost(buildHttpPost(url, headers, params, files), STRING_RESPONSE_HANDLER);
    }

    /**
     * 提交json格式数据到指定url
     *
     * @param url  url,可包含查询字符中
     * @param json json格式的字符串数据
     * @return 指定url响应的字符串数据
     */
    public static String postJson(String url, String json) {
        return post(url, json, MimeTypeEnum.JSON);
    }

    /**
     * 提交json格式数据到指定url
     *
     * @param url     url,可包含查询字符中
     * @param headers headers头部
     * @param json    json格式的字符串数据
     * @return 指定url响应的字符串数据
     */
    public static String postJson(String url, Map<String, String> headers, String json) {
        return post(url, headers, json, MimeTypeEnum.JSON);
    }

    /**
     * 提交xml格式数据到指定url
     *
     * @param url url，可以包含查询字符串
     * @param xml xml格式的字符串数据
     * @return 指定url响应的字符串数据
     */
    public static String postXml(String url, String xml) {
        return post(url, xml, MimeTypeEnum.XML);
    }

    /**
     * 提交xml格式数据到指定url
     *
     * @param url     url，可以包含查询字符串
     * @param headers headers头部
     * @param xml     xml格式的字符串数据
     * @return 指定url响应的字符串数据
     */
    public static String postXml(String url, Map<String, String> headers, String xml) {
        return post(url, headers, xml, MimeTypeEnum.XML);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url
     *
     * @param url          url，可以包含查询字符串
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, String data, MimeTypeEnum dataTypeEnum) {
        return sendPost(buildHttpPost(url, null, data, dataTypeEnum), STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url
     *
     * @param url          url，可以包含查询字符串
     * @param headers      headers头部
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, Map<String, String> headers, String data, MimeTypeEnum dataTypeEnum) {
        return sendPost(buildHttpPost(url, headers, data, dataTypeEnum), STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url
     *
     * @param url          url，可以包含查询字符串
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @param cookieList   提交到服务端的cookie数据
     * @param returnCookie 是否需要从服务端获取cookie数据
     * @return 指定url响应的字符串数据，并获取由服务端返回的cookie数据
     */
    public static String post(String url, String data, MimeTypeEnum dataTypeEnum,
                              List<Cookie> cookieList, boolean returnCookie) {
        return sendPost(buildHttpPost(url, null, data, dataTypeEnum), cookieList, returnCookie, STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url
     *
     * @param url          url，可以包含查询字符串
     * @param headers      headers头部
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @param cookieList   提交到服务端的cookie数据
     * @param returnCookie 是否需要从服务端获取cookie数据
     * @return 指定url响应的字符串数据，并获取由服务端返回的cookie数据
     */
    public static String post(String url, Map<String, String> headers, String data, MimeTypeEnum dataTypeEnum,
                              List<Cookie> cookieList, boolean returnCookie) {
        return sendPost(buildHttpPost(url, headers, data, dataTypeEnum), cookieList, returnCookie, STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url，并可指定数字证书
     *
     * @param url          url，可以包含查询字符串
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @param certPath     证书路径
     * @param certPassword 证书密码
     * @param certType     证书类型
     * @param sslProtocols ssl协议
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, String data, MimeTypeEnum dataTypeEnum, String certPath, String certPassword,
                              String certType, String[] sslProtocols) {
        return sendPost(buildHttpPost(url, null, data, dataTypeEnum),
                certPath, certPassword, certType, sslProtocols, STRING_RESPONSE_HANDLER);
    }

    /**
     * 通过指定的数据类型把数据post提交到指定的url，并可指定数字证书
     *
     * @param url          url，可以包含查询字符串
     * @param headers      headers头部
     * @param data         需要提交的数据
     * @param dataTypeEnum 提交数据的MIME type
     * @param certPath     证书路径
     * @param certPassword 证书密码
     * @param certType     证书类型
     * @param sslProtocols ssl协议
     * @return 指定url响应的字符串数据
     */
    public static String post(String url, Map<String, String> headers, String data, MimeTypeEnum dataTypeEnum,
                              String certPath, String certPassword, String certType, String[] sslProtocols) {
        return sendPost(buildHttpPost(url, headers, data, dataTypeEnum),
                certPath, certPassword, certType, sslProtocols, STRING_RESPONSE_HANDLER);
    }

    /**
     * 构建http post请求
     *
     * @param url     请求url
     * @param headers headers头部
     * @return
     */
    public static HttpPost buildHttpPost(String url, Map<String, String> headers) {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        return httpPost;
    }

    /**
     * 构建http post请求
     *
     * @param url     请求url
     * @param headers headers头部
     * @param params  post请求发送的参数，参数为param1=value1&param2=value2形式
     * @return
     */
    public static HttpPost buildHttpPost(String url, Map<String, String> headers, String params) {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        StringEntity stringEntity = new StringEntity(params, CharsetEnum.UTF8.getValue());
        stringEntity.setContentType(ContentTypeEnum.FORM_URLENCODED.getValue());
        httpPost.setEntity(stringEntity);
        return httpPost;
    }

    /**
     * 构建http post请求
     *
     * @param url     请求url
     * @param headers headers头部
     * @param params  请求参数map
     * @return
     */
    public static HttpPost buildHttpPost(String url, Map<String, String> headers, Map<String, String> params) {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        List<NameValuePair> nameValuePairs = new ArrayList<>();
        Set<Map.Entry<String, String>> entrySet = params.entrySet();
        for (Map.Entry<String, String> entry : entrySet) {
            nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, CharsetEnum.UTF8.getValue()));
            return httpPost;
        } catch (UnsupportedEncodingException e) {
            log.error("http post not supported encoding: {}", e.getMessage());
            // @SimplifiedChinese=HTTP POST请求失败
            // @English=Http Post error
            throw new CommonException("cn.yx.common.core.executeHttpPostError", e);
        }
    }

    /**
     * 构建http post请求
     *
     * @param url          请求url
     * @param headers      headers头部
     * @param data         请求的字符串数据，如json或xml
     * @param dataTypeEnum 指定请求的数据类型
     * @return
     */
    public static HttpPost buildHttpPost(String url, Map<String, String> headers, String data, MimeTypeEnum dataTypeEnum) {
        HttpPost httpPost = new HttpPost(url);
        refreshHeaders(headers, dataTypeEnum);
        setHeaders(httpPost, headers);
        httpPost.setEntity(new StringEntity(data, CharsetEnum.UTF8.getValue()));
        return httpPost;
    }

    /**
     * 构建http post请求
     *
     * @param url     请求url
     * @param headers headers头部
     * @param params  请求参数map
     * @param files   文件map
     * @return
     */
    public static HttpPost buildHttpPost(String url, Map<String, String> headers, Map<String, String> params, Map<String, File> files) {
        HttpPost httpPost = new HttpPost(url);
        setHeaders(httpPost, headers);
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
        Set<Map.Entry<String, String>> paramEntrySet = params.entrySet();
        for (Map.Entry<String, String> entry : paramEntrySet) {
            multipartEntityBuilder.addPart(entry.getKey(), new StringBody(entry.getValue(), ContentType.TEXT_PLAIN));
        }
        Set<Map.Entry<String, File>> fileEntrySet = files.entrySet();
        for (Map.Entry<String, File> entry : fileEntrySet) {
            multipartEntityBuilder.addPart(entry.getKey(), new FileBody(entry.getValue()));
        }
        HttpEntity entity = multipartEntityBuilder.build();
        httpPost.setEntity(entity);
        return httpPost;
    }

    /**
     * 发送HTTP POST请求，并且需要指定相应的ResponseHandler
     *
     * @param httpPost        HttpPost请求
     * @param responseHandler ResponseHandler响应处理对象
     * @param <T>             响应处理对象中可通过泛型来指定返回的具体数据类型
     * @return 通过在ResponseHandler中指定的具体类型来返回响应数据
     */
    public static <T> T sendPost(HttpPost httpPost, ResponseHandler<T> responseHandler) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            return executePost(httpClient, httpPost, responseHandler);
        } catch (IOException e) {
            log.error("http client execute post error: {}", e.getMessage());
            throw new CommonException("cn.yx.common.core.executeHttpPostError", e);
        }
    }

    /**
     * 发送HTTP POST请求，并且需要指定相应的ResponseHandler
     *
     * @param httpPost        HttpPost请求
     * @param cookieList      需要提交到服务端的cookie数据
     * @param returnCookie    是否需要从服务端获取cookie数据
     * @param responseHandler ResponseHandler响应处理对象
     * @param <T>             响应处理对象中可通过泛型来指定返回的具体数据类型
     * @return 通过在ResponseHandler中指定的具体类型来返回响应数据，并获取由服务端返回的cookie数据到cookies成员变量中
     */
    public static <T> T sendPost(HttpPost httpPost, List<Cookie> cookieList, boolean returnCookie, ResponseHandler<T> responseHandler) {
        CookieStore cookieStore = getCookieStore(cookieList);
        try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build()) {
            T t = executePost(httpClient, httpPost, responseHandler);
            if (returnCookie) {
                setCookies(cookieStore.getCookies());
            }
            return t;
        } catch (IOException e) {
            log.error("http client execute post error: {}", e.getMessage());
            throw new CommonException("cn.yx.common.core.executeHttpPostError", e);
        }
    }

    /**
     * 发送HTTP POST请求，并且需要指定相应的ResponseHandler，同时还可以指定数字证书与协议
     *
     * @param httpPost        HttpPost请求
     * @param certPath        证书路径
     * @param certPassword    证书密码
     * @param certType        证书类型
     * @param sslProtocols    ssl协议数组
     * @param responseHandler ResponseHandler响应处理对象
     * @param <T>             响应处理对象中可通过泛型来指定返回的具体数据类型
     * @return 通过在ResponseHandler中指定的具体类型来返回响应数据
     */
    public static <T> T sendPost(HttpPost httpPost, String certPath, String certPassword,
                                 String certType, String[] sslProtocols, ResponseHandler<T> responseHandler) {
        try (CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(
                getSslConnectionSocketFactory(certPath, certPassword, certType, sslProtocols)).build()) {
            return executePost(httpClient, httpPost, responseHandler);
        } catch (IOException e) {
            log.error("http client execute post error: {}", e.getMessage());
            throw new CommonException("cn.yx.common.core.executeHttpPostError", e);
        }
    }

    /**
     * 执行http post请求
     *
     * @param httpClient
     * @param httpPost
     * @param responseHandler
     * @param <T>
     * @return
     */
    private static <T> T executePost(HttpClient httpClient, HttpPost httpPost, ResponseHandler<T> responseHandler) {
        try {
            httpPost.setConfig(requestConfig);
            return httpClient.execute(httpPost, responseHandler);
        } catch (IOException e) {
            log.error("http client execute post error: {}", e.getMessage());
            throw new CommonException("cn.yx.common.core.executeHttpPostError", e);
        }
    }

}
